Jest์ ํ์ ์์ ์ฑ ํตํฉ์ผ๋ก TypeScript ํ ์คํ ์ ๊ฐํํ์ธ์. ๊ฐ๋ ฅํ๊ณ ์ ์ง ๊ด๋ฆฌ๊ฐ ์ฉ์ดํ ์ฝ๋๋ฅผ ์ํ ๋ชจ๋ฒ ์ฌ๋ก, ์ค์ฉ์ ์ธ ์์ ๋ฐ ์ ๋ต์ ๋ฐฐ์๋๋ค.
Mastering Type Safety in TypeScript Testing: A Jest Integration Guide
์ํํธ์จ์ด ๊ฐ๋ฐ ํ๊ฒฝ์ด ๋์์์ด ์งํํจ์ ๋ฐ๋ผ ์ฝ๋ ํ์ง์ ์ ์งํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฑ์ ๋ณด์ฅํ๋ ๊ฒ์ด ๋ฌด์๋ณด๋ค ์ค์ํฉ๋๋ค. TypeScript๋ ์ ์ ํ์ดํ ๊ธฐ๋ฅ์ผ๋ก ๊ฐ๋ ฅํ๊ณ ์ ์ง ๊ด๋ฆฌ๊ฐ ์ฉ์ดํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ๋ฐ ์ ๋์ ์ธ ์ ํ์ผ๋ก ๋ ์ฌ๋์ต๋๋ค. ๊ทธ๋ฌ๋ TypeScript์ ์ด์ ์ ๊ฐ๋ฐ ๋จ๊ณ๋ฅผ ๋์ด ํ ์คํ ์ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ์ด ๊ฐ์ด๋์์๋ ๋๋ฆฌ ์ฌ์ฉ๋๋ JavaScript ํ ์คํ ํ๋ ์์ํฌ์ธ Jest๋ฅผ ํ์ฉํ์ฌ TypeScript ํ ์คํ ์ํฌํ๋ก์ ํ์ ์์ ์ฑ์ ์ํํ๊ฒ ํตํฉํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ด ๋๋ค. ํจ๊ณผ์ ์ด๊ณ ์ ์ง ๊ด๋ฆฌ ๊ฐ๋ฅํ ํ ์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ํ ๋ชจ๋ฒ ์ฌ๋ก, ์ค์ฉ์ ์ธ ์์ ๋ฐ ์ ๋ต์ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
The Significance of Type Safety in Testing
ํ์ ์์ ์ฑ์ ํต์ฌ์ ์ผ๋ก ๊ฐ๋ฐ์๊ฐ ๋ฐํ์์ด ์๋ ๊ฐ๋ฐ ํ๋ก์ธ์ค ์ค์ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ ์ ์๋๋ก ํฉ๋๋ค. ์ด๋ ํนํ ํ ์คํธ์์ ์ ๋ฆฌํ๋ฉฐ, ํ์ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ์กฐ๊ธฐ์ ๊ฐ์งํ๋ฉด ๋์ค์ ์๋นํ ๋๋ฒ๊น ๋ ธ๋ ฅ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค. ํ ์คํ ์ ํ์ ์์ ์ฑ์ ํตํฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์ฃผ์ ์ด์ ์ด ์์ต๋๋ค.
- Early Error Detection: TypeScript์ ํ์ ๊ฒ์ฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ๋ฐํ์ ์ค๋ฅ๋ก ๋ํ๋๊ธฐ ์ ์ ํ ์คํธ ์ปดํ์ผ ์ค์ ํ์ ๋ถ์ผ์น, ์๋ชป๋ ์ธ์ ํ์ ๋ฐ ๊ธฐํ ํ์ ๊ด๋ จ ์ค๋ฅ๋ฅผ ์๋ณํ ์ ์์ต๋๋ค.
- Improved Code Maintainability: ํ์ ์ฃผ์์ ์ด์์๋ ๋ฌธ์ ์ญํ ์ ํ์ฌ ์ฝ๋๋ฅผ ๋ ์ฝ๊ฒ ์ดํดํ๊ณ ์ ์ง ๊ด๋ฆฌํ ์ ์๋๋ก ํฉ๋๋ค. ํ ์คํธ๋ฅผ ํ์ ๊ฒ์ฌํ๋ฉด ์ด๋ฌํ ์ฃผ์์ด ๊ฐํ๋๊ณ ์ฝ๋๋ฒ ์ด์ค ์ ์ฒด์์ ์ผ๊ด์ฑ์ด ์ ์ง๋ฉ๋๋ค.
- Enhanced Refactoring Capabilities: ๋ฆฌํฉํฐ๋ง์ด ๋ ์์ ํ๊ณ ํจ์จ์ ์ผ๋ก ๋ฉ๋๋ค. TypeScript์ ํ์ ๊ฒ์ฌ๋ ๋ณ๊ฒฝ์ผ๋ก ์ธํด ์๋ํ์ง ์์ ๊ฒฐ๊ณผ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ๊ธฐ์กด ํ ์คํธ๊ฐ ์ค๋จ๋์ง ์๋๋ก ํฉ๋๋ค.
- Reduced Bugs: ํ์ ๊ด๋ จ ์ค๋ฅ๋ฅผ ์กฐ๊ธฐ์ ํฌ์ฐฉํ๋ฉด ํ๋ก๋์ ์ ๋๋ฌํ๋ ๋ฒ๊ทธ ์๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ต๋๋ค.
- Increased Confidence: ํ์ ์ด ์ ์ง์ ๋๊ณ ํ ์คํธ๊ฐ ์ ๋ ์ฝ๋๋ ๊ฐ๋ฐ์์๊ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋ํ ํ์ ์ ๋์ฌ์ค๋๋ค.
Setting up Jest with TypeScript
Jest๋ฅผ TypeScript์ ํตํฉํ๋ ๊ฒ์ ๊ฐ๋จํ ํ๋ก์ธ์ค์ ๋๋ค. ๋ค์์ ๋จ๊ณ๋ณ ๊ฐ์ด๋์ ๋๋ค.
- Project Initialization: TypeScript ํ๋ก์ ํธ๊ฐ ์์ง ์๋ ๊ฒฝ์ฐ ๋จผ์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ญ๋๋ค. npm ๋๋ yarn์ ์ฌ์ฉํ์ฌ ์ ํ๋ก์ ํธ๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
npm init -y # or yarn init -y - Install TypeScript and Jest: ํ์ํ ํจํค์ง๋ฅผ ๊ฐ๋ฐ ์ข
์์ฑ์ผ๋ก ์ค์นํฉ๋๋ค.
npm install --save-dev typescript jest @types/jest ts-jest # or yarn add --dev typescript jest @types/jest ts-jesttypescript: TypeScript ์ปดํ์ผ๋ฌ์ ๋๋ค.jest: ํ ์คํ ํ๋ ์์ํฌ์ ๋๋ค.@types/jest: Jest์ ๋ํ ํ์ ์ ์์ ๋๋ค.ts-jest: Jest์ฉ TypeScript ๋ณํ๊ธฐ๋ก, TypeScript ์ฝ๋๋ฅผ ์ดํดํ ์ ์๋๋ก ํฉ๋๋ค.
- Configure TypeScript: ํ๋ก์ ํธ์ ๋ฃจํธ ๋๋ ํฐ๋ฆฌ์
tsconfig.jsonํ์ผ์ ๋ง๋ญ๋๋ค. ์ด ํ์ผ์ TypeScript์ ๋ํ ์ปดํ์ผ๋ฌ ์ต์ ์ ์ง์ ํฉ๋๋ค. ๊ธฐ๋ณธ ๊ตฌ์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.{ "compilerOptions": { "target": "es5", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "outDir": "./dist" }, "include": ["src/**/*", "test/**/*"], "exclude": ["node_modules"] }์ฃผ์ ์ค์ :
-
target: ํ๊ฒํ ํ JavaScript ๋ฒ์ ์ ์ง์ ํฉ๋๋ค(์: es5, es6, esnext). -
module: ์ฌ์ฉํ ๋ชจ๋ ์์คํ ์ ์ง์ ํฉ๋๋ค(์: commonjs, esnext). -
esModuleInterop: CommonJS์ ES ๋ชจ๋ ๊ฐ์ ์ํธ ์ด์ฉ์ฑ์ ํ์ฑํํฉ๋๋ค. -
forceConsistentCasingInFileNames: ํ์ผ ์ด๋ฆ์ ์ผ๊ด๋ ๋์๋ฌธ์ ๊ตฌ๋ถ์ ์ ์ฉํฉ๋๋ค. -
strict: ์๊ฒฉํ ํ์ ๊ฒ์ฌ๋ฅผ ํ์ฑํํฉ๋๋ค. ํ์ ์์ ์ฑ์ ๊ฐ์ ํ๋ ๋ฐ ๊ถ์ฅ๋ฉ๋๋ค. -
skipLibCheck: ์ ์ธ ํ์ผ(.d.ts)์ ํ์ ๊ฒ์ฌ๋ฅผ ๊ฑด๋๋๋๋ค. -
outDir: ์ปดํ์ผ๋ JavaScript ํ์ผ์ ์ถ๋ ฅ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ง์ ํฉ๋๋ค. -
include: ์ปดํ์ผ์ ํฌํจํ ํ์ผ ๋ฐ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ง์ ํฉ๋๋ค. -
exclude: ์ปดํ์ผ์์ ์ ์ธํ ํ์ผ ๋ฐ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ง์ ํฉ๋๋ค.
-
- Configure Jest: ํ๋ก์ ํธ์ ๋ฃจํธ ๋๋ ํฐ๋ฆฌ์
jest.config.js(๋๋jest.config.ts) ํ์ผ์ ๋ง๋ญ๋๋ค. ์ด ํ์ผ์ Jest๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. TypeScript๋ฅผ ์ง์ํ๋ ๊ธฐ๋ณธ ๊ตฌ์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค./** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { preset: 'ts-jest', testEnvironment: 'node', testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], transform: { '^.+\.(ts|tsx)?$': 'ts-jest', }, moduleNameMapper: { '^@/(.*)$': '/src/$1', }, collectCoverage: false, coverageDirectory: 'coverage', }; preset: 'ts-jest': ts-jest๋ฅผ ์ฌ์ฉํ๊ณ ์์์ ์ง์ ํฉ๋๋ค.testEnvironment: ํ ์คํ ํ๊ฒฝ์ ์ค์ ํฉ๋๋ค(์: ๋ธ๋ผ์ฐ์ ์ ์ ์ฌํ ํ๊ฒฝ์ ๊ฒฝ์ฐ 'node', 'jsdom').testMatch: ํ ์คํธ ํ์ผ๊ณผ ์ผ์นํ๋ ํ์ผ ํจํด์ ์ ์ํฉ๋๋ค.transform: ํ์ผ์ ์ฌ์ฉํ ๋ณํ๊ธฐ๋ฅผ ์ง์ ํฉ๋๋ค. ์ฌ๊ธฐ์๋ts-jest๋ฅผ ์ฌ์ฉํ์ฌ TypeScript ํ์ผ์ ๋ณํํฉ๋๋ค.moduleNameMapper: ๋ชจ๋ ์จ๋ฆฌ์ด์ฑ์ ์ฌ์ฉ๋๋ฉฐ, ํนํ ๊ธด ์๋ ๊ฒฝ๋ก ๋์ `@/components`์ ๊ฐ์ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ธ์ค๊ธฐ ๊ฒฝ๋ก๋ฅผ ํด๊ฒฐํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.collectCoverage: ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ์ฑํํ๊ฑฐ๋ ๋นํ์ฑํํฉ๋๋ค.coverageDirectory: ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์์ ๋ํ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ค์ ํฉ๋๋ค.
- Write Tests: ํ
์คํธ ํ์ผ(์:
src/my-component.test.ts๋๋src/__tests__/my-component.test.ts)์ ๋ง๋ญ๋๋ค. - Run Tests:
package.json์ ํ ์คํธ ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํฉ๋๋ค."scripts": { "test": "jest" }๊ทธ๋ฐ ๋ค์ ๋ค์์ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค.
npm test # or yarn test
Example: Testing a Simple Function
ํ์ ์์ ํ ์คํธ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฐ๋จํ ์๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ๋ ์ซ์๋ฅผ ๋ํ๋ ํจ์๋ฅผ ๊ณ ๋ คํด ๋ณด์ธ์.
// src/math.ts
export function add(a: number, b: number): number {
return a + b;
}
์ด์ Jest์ TypeScript๋ฅผ ์ฌ์ฉํ์ฌ ์ด ํจ์์ ๋ํ ํ ์คํธ๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
// src/math.test.ts
import { add } from './math';
test('adds two numbers correctly', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});
test('handles non-numeric input (incorrectly)', () => {
// @ts-expect-error: TypeScript will catch this error if uncommented
// expect(add('2', 3)).toBe(5);
});
์ด ์์์:
addํจ์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.- Jest์
test๋ฐexpectํจ์๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๋ฅผ ์์ฑํฉ๋๋ค. - ํ ์คํธ๋ ๋ค์ํ ์ ๋ ฅ์ผ๋ก ํจ์์ ๋์์ ํ์ธํฉ๋๋ค.
- ์ฃผ์ ์ฒ๋ฆฌ๋ ์ค์ ๋ฌธ์์ด์
addํจ์์ ์ ๋ฌํ๋ ค๊ณ ์๋ํ๋ฉด TypeScript๊ฐ ํ์ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ์ฌ ์ด๋ฌํ ์ค์๊ฐ ๋ฐํ์์ ๋๋ฌํ๋ ๊ฒ์ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. `//@ts-expect-error` ์ฃผ์์ TypeScript์๊ฒ ํด๋น ์ค์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ์ผ๋ก ์์ํ๋๋ก ์ง์ํฉ๋๋ค.
Advanced Testing Techniques with TypeScript and Jest
๊ธฐ๋ณธ ์ค์ ์ ์๋ฃํ์ผ๋ฉด ๊ณ ๊ธ ํ ์คํธ ๊ธฐ์ ์ ํ์ํ์ฌ ํ ์คํธ ์ค์ํธ์ ํจ๊ณผ์ ์ ์ง ๊ด๋ฆฌ์ฑ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
Mocking and Spies
๋ชจ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ธ๋ถ ์ข ์์ฑ์ ์ ์ด๋ ๋์ฒด๋ฌผ๋ก ๋์ฒดํ์ฌ ์ฝ๋ ๋จ์๋ฅผ ๊ฒฉ๋ฆฌํ ์ ์์ต๋๋ค. Jest๋ ๊ธฐ๋ณธ ์ ๊ณต ๋ชจ์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
Example: API ํธ์ถ์ ์ํํ๋ ํจ์ ๋ชจ์:
// src/api.ts
export async function fetchData(url: string): Promise<any> {
const response = await fetch(url);
return response.json();
}
// src/my-component.ts
import { fetchData } from './api';
export async function processData() {
const data = await fetchData('https://example.com/api/data');
// Process the data
return data;
}
// src/my-component.test.ts
import { processData } from './my-component';
import { fetchData } from './api';
jest.mock('./api'); // Mock the api module
test('processes data correctly', async () => {
// @ts-ignore: Ignoring the type error for this test
fetchData.mockResolvedValue({ result: 'success' }); // Mock the resolved value
const result = await processData();
expect(result).toEqual({ result: 'success' });
expect(fetchData).toHaveBeenCalledWith('https://example.com/api/data');
});
์ด ์์์๋ api.ts ๋ชจ๋์์ fetchData ํจ์๋ฅผ ๋ชจ์ํฉ๋๋ค. mockResolvedValue๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ๊ณต์ ์ธ API ์๋ต์ ์๋ฎฌ๋ ์ด์
ํ๊ณ processData๊ฐ ๋ชจ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ๋์ง ํ์ธํฉ๋๋ค. toHaveBeenCalledWith๋ฅผ ์ฌ์ฉํ์ฌ `fetchData` ํจ์๊ฐ ์ฌ๋ฐ๋ฅธ ์ธ์๋ก ํธ์ถ๋์๋์ง ํ์ธํฉ๋๋ค.
Testing Asynchronous Code
๋น๋๊ธฐ ์ฝ๋๋ฅผ ํ ์คํธํ๋ ๊ฒ์ ์ต์ JavaScript ์ ํ๋ฆฌ์ผ์ด์ ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. Jest๋ ๋น๋๊ธฐ ํ ์คํธ๋ฅผ ์ฒ๋ฆฌํ๋ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
Example: setTimeout์ ์ฌ์ฉํ๋ ํจ์ ํ
์คํธ:
// src/async.ts
export function delayedGreeting(name: string, delay: number): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Hello, ${name}!`);
}, delay);
});
}
// src/async.test.ts
import { delayedGreeting } from './async';
test('greets with a delay', async () => {
const greeting = await delayedGreeting('World', 100);
expect(greeting).toBe('Hello, World!');
});
์ด ์์์๋ async/await๋ฅผ ์ฌ์ฉํ์ฌ ํ
์คํธ ๋ด์์ ๋น๋๊ธฐ ์์
์ ์ฒ๋ฆฌํฉ๋๋ค. Jest๋ ๋น๋๊ธฐ ํ
์คํธ์ ์ฝ๋ฐฑ ๋ฐ ํ๋ก๋ฏธ์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ ์ง์ํฉ๋๋ค.
Code Coverage
์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์๋ ์ฝ๋์ ์ด๋ค ๋ถ๋ถ์ด ํ ์คํธ๋ก ์ปค๋ฒ๋๋์ง์ ๋ํ ๊ท์คํ ํต์ฐฐ๋ ฅ์ ์ ๊ณตํฉ๋๋ค. Jest๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์์ต๋๋ค.
์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ์ฑํํ๋ ค๋ฉด jest.config.js ํ์ผ์์ collectCoverage ๋ฐ coverageDirectory ์ต์
์ ๊ตฌ์ฑํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ์ฑํํ ์ํ์์ ํ
์คํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค.
// jest.config.js
module.exports = {
// ... other configurations
collectCoverage: true,
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'], // Specify files to collect coverage from
coverageThreshold: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
};
collectCoverageFrom ์ต์
์ ์ฌ์ฉํ๋ฉด ์ปค๋ฒ๋ฆฌ์ง์ ๋ํด ๊ณ ๋ คํด์ผ ํ๋ ํ์ผ์ ์ง์ ํ ์ ์์ต๋๋ค. coverageThreshold ์ต์
์ ์ฌ์ฉํ๋ฉด ์ต์ ์ปค๋ฒ๋ฆฌ์ง ๋ฐฑ๋ถ์จ์ ์ค์ ํ ์ ์์ต๋๋ค. ํ
์คํธ๋ฅผ ์คํํ๋ฉด Jest๋ ์ง์ ๋ ๋๋ ํฐ๋ฆฌ์ ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์๋ฅผ ์์ฑํฉ๋๋ค.
์์ธํ ํต์ฐฐ๋ ฅ์ ์ป๊ธฐ ์ํด HTML ํ์์ผ๋ก ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
Test-Driven Development (TDD) with TypeScript and Jest
ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)์ ์ค์ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ ๊ฐ์กฐํ๋ ์ํํธ์จ์ด ๊ฐ๋ฐ ํ๋ก์ธ์ค์ ๋๋ค. TDD๋ ๋งค์ฐ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด ๋ ์ ์์ผ๋ฉฐ, ๋ ๊ฐ๋ ฅํ๊ณ ์ ์ค๊ณ๋ ์ฝ๋๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. TypeScript์ Jest๋ฅผ ์ฌ์ฉํ๋ฉด TDD ํ๋ก์ธ์ค๊ฐ ๊ฐ์ํ๋ฉ๋๋ค.
- Write a Failing Test: ์ฝ๋์ ์ํ๋ ๋์์ ์ค๋ช ํ๋ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ผ๋ก ์์ํฉ๋๋ค. ์ฝ๋๊ฐ ์์ง ์กด์ฌํ์ง ์์ผ๋ฏ๋ก ํ ์คํธ๊ฐ ์ด๊ธฐ์ ์คํจํด์ผ ํฉ๋๋ค.
- Write the Minimum Code to Pass the Test: ํ ์คํธ๋ฅผ ํต๊ณผํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋งค์ฐ ๊ธฐ๋ณธ์ ์ธ ๊ตฌํ์ด ํฌํจ๋ ์ ์์ต๋๋ค.
- Refactor: ํ ์คํธ๊ฐ ํต๊ณผ๋๋ฉด ์ฝ๋๋ฅผ ๋ฆฌํฉํฐ๋งํ์ฌ ๋์์ธ๊ณผ ๊ฐ๋ ์ฑ์ ๊ฐ์ ํ๋ ๋์์ ๋ชจ๋ ํ ์คํธ๊ฐ ์ฌ์ ํ ํต๊ณผํ๋์ง ํ์ธํฉ๋๋ค.
- Repeat: ๊ฐ ์ ๊ธฐ๋ฅ ๋๋ ๊ธฐ๋ฅ์ ๋ํด ์ด ์ฃผ๊ธฐ๋ฅผ ๋ฐ๋ณตํฉ๋๋ค.
Example: TDD๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์์ด์ ์ฒซ ๊ธ์๋ฅผ ๋๋ฌธ์๋ก ๋ฐ๊พธ๋ ํจ์๋ฅผ ๋น๋ํด ๋ณด๊ฒ ์ต๋๋ค.
- Failing Test:
// src/string-utils.test.ts
import { capitalizeFirstLetter } from './string-utils';
test('capitalizes the first letter of a string', () => {
expect(capitalizeFirstLetter('hello')).toBe('Hello');
});
- Minimum Code to Pass:
// src/string-utils.ts
export function capitalizeFirstLetter(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
- Refactor (if needed): ์ด ๊ฐ๋จํ ๊ฒฝ์ฐ ์ฝ๋๊ฐ ์ด๋ฏธ ๋น๊ต์ ๊น๋ํฉ๋๋ค. ๋ค๋ฅธ ์์ง ์ผ์ด์ค๋ฅผ ๋ค๋ฃจ๊ธฐ ์ํด ๋ ๋ง์ ํ ์คํธ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
// src/string-utils.test.ts (expanded)
import { capitalizeFirstLetter } from './string-utils';
test('capitalizes the first letter of a string', () => {
expect(capitalizeFirstLetter('hello')).toBe('Hello');
expect(capitalizeFirstLetter('world')).toBe('World');
expect(capitalizeFirstLetter('')).toBe('');
expect(capitalizeFirstLetter('123test')).toBe('123test');
});
TypeScript๋ฅผ ์ฌ์ฉํ TDD๋ ์ฒ์๋ถํฐ ํ ์คํธ๋ฅผ ์์ฑํ๋๋ก ๋ณด์ฅํ์ฌ ์ค๋ฅ๋ก๋ถํฐ ๋ณดํธํ๋ ์ฆ๊ฐ์ ์ธ ํ์ ์์ ์ฑ ์ด์ ์ ์ ๊ณตํฉ๋๋ค.
Best Practices for Type-Safe Testing
Jest ๋ฐ TypeScript๋ฅผ ์ฌ์ฉํ ํ์ ์์ ํ ์คํธ์ ์ด์ ์ ๊ทน๋ํํ๋ ค๋ฉด ๋ค์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ณ ๋ คํ์ธ์.
- Write Comprehensive Tests: ํ ์คํธ๊ฐ ๋ชจ๋ ๋ค์ํ ์ฝ๋ ๊ฒฝ๋ก์ ์์ง ์ผ์ด์ค๋ฅผ ๋ค๋ฃจ๋์ง ํ์ธํฉ๋๋ค. ๋์ ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ชฉํ๋ก ํฉ๋๋ค.
- Use Descriptive Test Names: ๊ฐ ํ ์คํธ์ ๋ชฉ์ ์ ์ค๋ช ํ๋ ๋ช ํํ๊ณ ์ค๋ช ์ ์ธ ํ ์คํธ ์ด๋ฆ์ ์์ฑํฉ๋๋ค.
- Leverage Type Annotations: ํ ์คํธ์์ ํ์ ์ฃผ์์ ๊ด๋ฒ์ํ๊ฒ ์ฌ์ฉํ์ฌ ๊ฐ๋ ์ฑ์ ๋์ด๊ณ ํ์ ๊ด๋ จ ์ค๋ฅ๋ฅผ ์กฐ๊ธฐ์ ํฌ์ฐฉํฉ๋๋ค.
- Mock Appropriately: ๋ชจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋ ๋จ์๋ฅผ ๊ฒฉ๋ฆฌํ๊ณ ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธํฉ๋๋ค. ํ ์คํธ๋ฅผ ๋ ํ์ค์ ์ผ๋ก ๋ง๋ค ์ ์๋ ๊ณผ๋ํ ๋ชจ์๋ ํผํฉ๋๋ค.
- Test Asynchronous Code Effectively: ๋น๋๊ธฐ ์ฝ๋๋ฅผ ํ
์คํธํ ๋
async/await๋๋ ํ๋ก๋ฏธ์ค๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํฉ๋๋ค. - Follow TDD Principles: TDD๋ฅผ ์ฑํํ์ฌ ๊ฐ๋ฐ ํ๋ก์ธ์ค๋ฅผ ์ฃผ๋ํ๊ณ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ํ ์คํธ๋ฅผ ์์ฑํ๋์ง ํ์ธํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- Maintain Testability: ํ ์คํธ ๊ฐ๋ฅ์ฑ์ ์ผ๋์ ๋๊ณ ์ฝ๋๋ฅผ ๋์์ธํฉ๋๋ค. ํจ์์ ๋ชจ๋์ ๋ช ํํ ์ ๋ ฅ ๋ฐ ์ถ๋ ฅ์ผ๋ก ์ง์ค์ ์ผ๋ก ์ ์งํฉ๋๋ค.
- Review Test Code: ํ๋ก๋์ ์ฝ๋๋ฅผ ๊ฒํ ํ๋ ๊ฒ์ฒ๋ผ ํ ์คํธ ์ฝ๋๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ๊ฒํ ํ์ฌ ์ ์ง ๊ด๋ฆฌ ๊ฐ๋ฅํ๊ณ ํจ๊ณผ์ ์ด๋ฉฐ ์ต์ ์ํ์ธ์ง ํ์ธํฉ๋๋ค. CI/CD ํ์ดํ๋ผ์ธ ๋ด์์ ํ ์คํธ ์ฝ๋ ํ์ง ๊ฒ์ฌ๋ฅผ ๊ณ ๋ คํ์ญ์์ค.
- Keep Tests Up-to-Date: ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ ๋ ๊ทธ์ ๋ฐ๋ผ ํ ์คํธ๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค. ์ค๋๋ ํ ์คํธ๋ ์คํ์ผ๋ก ์ด์ด์ง ์ ์์ผ๋ฉฐ ํ ์คํธ ์ค์ํธ์ ๊ฐ์น๋ฅผ ๋จ์ด๋จ๋ฆด ์ ์์ต๋๋ค.
- Integrate Tests into CI/CD: ํ ์คํธ๋ฅผ ์ง์์ ํตํฉ ๋ฐ ์ง์์ ๋ฐฐํฌ(CI/CD) ํ์ดํ๋ผ์ธ์ ํตํฉํ์ฌ ํ ์คํธ๋ฅผ ์๋ํํ๊ณ ๊ฐ๋ฐ ์ฃผ๊ธฐ ์ด๊ธฐ์ ๋ฌธ์ ๋ฅผ ํฌ์ฐฉํฉ๋๋ค. ์ด๋ ํนํ ์ฌ๋ฌ ์๊ฐ๋์ ์์น์์ ์ฝ๋ ๋ณ๊ฒฝ์ด ์ด๋ฃจ์ด์ง ์ ์๋ ๊ธ๋ก๋ฒ ๊ฐ๋ฐ ํ์ ์ ์ฉํฉ๋๋ค.
Common Pitfalls and Troubleshooting
Jest์ TypeScript๋ฅผ ํตํฉํ๋ ๊ฒ์ ์ผ๋ฐ์ ์ผ๋ก ๊ฐ๋จํ์ง๋ง ๋ช ๊ฐ์ง ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ค์์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐ ๋์์ด ๋๋ ๋ช ๊ฐ์ง ํ์ ๋๋ค.
- Type Errors in Tests: ํ ์คํธ์์ ํ์ ์ค๋ฅ๊ฐ ํ์๋๋ฉด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ฃผ์ ๊น๊ฒ ๊ฒ์ฌํฉ๋๋ค. ์ด๋ฌํ ๋ฉ์์ง๋ ์ข ์ข ๋ฌธ์ ๊ฐ ์๋ ํน์ ์ฝ๋ ์ค์ ๊ฐ๋ฆฌํต๋๋ค. ํ์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์๋์๋์ง, ํจ์์ ์ฌ๋ฐ๋ฅธ ์ธ์๋ฅผ ์ ๋ฌํ๊ณ ์๋์ง ํ์ธํฉ๋๋ค.
- Incorrect Import Paths: ํนํ ๋ชจ๋ ๋ณ์นญ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๊ฐ์ ธ์ค๊ธฐ ๊ฒฝ๋ก๊ฐ ์ฌ๋ฐ๋ฅธ์ง ํ์ธํฉ๋๋ค.
tsconfig.json๋ฐ Jest ๊ตฌ์ฑ์ ๋ค์ ํ์ธํฉ๋๋ค. - Jest Configuration Issues:
jest.config.jsํ์ผ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑ๋์๋์ง ํ์ธํ๊ธฐ ์ํด ์ฃผ์ ๊น๊ฒ ๊ฒํ ํฉ๋๋ค.preset,transform๋ฐtestMatch์ต์ ์ ์ฃผ์ํ์ญ์์ค. - Outdated Dependencies: ๋ชจ๋ ์ข
์์ฑ(TypeScript, Jest,
ts-jest๋ฐ ํ์ ์ ์)์ด ์ต์ ์ํ์ธ์ง ํ์ธํฉ๋๋ค. - Testing Environment Mismatches: ํน์ ํ๊ฒฝ(์: ๋ธ๋ผ์ฐ์ )์์ ์คํ๋๋ ์ฝ๋๋ฅผ ํ
์คํธํ๋ ๊ฒฝ์ฐ Jest ํ
์คํธ ํ๊ฒฝ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑ๋์๋์ง ํ์ธํฉ๋๋ค(์:
jsdom์ฌ์ฉ). - Mocking Issues: ๋ชจ์ ๊ตฌ์ฑ์ ๋ค์ ํ์ธํฉ๋๋ค. ํ
์คํธ๋ฅผ ์คํํ๊ธฐ ์ ์ ๋ชจ์๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์๋์ง ํ์ธํฉ๋๋ค.
mockResolvedValue,mockRejectedValue๋ฐ ๊ธฐํ ๋ชจ์ ๋ฐฉ๋ฒ์ ์ ์ ํ๊ฒ ์ฌ์ฉํฉ๋๋ค. - Asynchronous Test Issues: ๋น๋๊ธฐ ์ฝ๋๋ฅผ ํ
์คํธํ ๋ ํ
์คํธ๊ฐ ํ๋ก๋ฏธ์ค๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ๊ฑฐ๋
async/await๋ฅผ ์ฌ์ฉํ๋์ง ํ์ธํฉ๋๋ค.
Conclusion
ํ์ ์์ ํ ์คํธ๋ฅผ ์ํด Jest๋ฅผ TypeScript์ ํตํฉํ๋ ๊ฒ์ ์ฝ๋ ํ์ง์ ๊ฐ์ ํ๊ณ ๋ฒ๊ทธ๋ฅผ ์ค์ด๋ฉฐ ๊ฐ๋ฐ ํ๋ก์ธ์ค๋ฅผ ๊ฐ์ํํ๋ ๋ฐ ๋งค์ฐ ํจ๊ณผ์ ์ธ ์ ๋ต์ ๋๋ค. ์ด ๊ฐ์ด๋์ ์ค๋ช ๋ ๋ชจ๋ฒ ์ฌ๋ก์ ๊ธฐ์ ์ ๋ฐ๋ฅด๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๋ฐ์ ์ธ ์ ๋ขฐ์ฑ์ ๊ธฐ์ฌํ๋ ๊ฐ๋ ฅํ๊ณ ์ ์ง ๊ด๋ฆฌ ๊ฐ๋ฅํ ํ ์คํธ๋ฅผ ๋น๋ํ ์ ์์ต๋๋ค. ํ ์คํธ ์ ๊ทผ ๋ฐฉ์์ ์ง์์ ์ผ๋ก ๊ฐ์ ํ๊ณ ํ๋ก์ ํธ์ ํน์ ์๊ตฌ ์ฌํญ์ ๋ง๊ฒ ์กฐ์ ํ๋ ๊ฒ์ ์์ง ๋ง์ญ์์ค.
ํ ์คํธ์์ ํ์ ์์ ์ฑ์ ์์ฉํ๋ ๊ฒ์ ๋จ์ํ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ๋ ๊ฒ์ด ์๋๋๋ค. ์ฝ๋๋ฒ ์ด์ค์ ๋ํ ํ์ ์ ๊ตฌ์ถํ๊ณ ๊ธ๋ก๋ฒ ํ ๋ด์์ ํ์ ์ ์ด์งํ๋ฉฐ ๊ถ๊ทน์ ์ผ๋ก ๋ ๋์ ์ํํธ์จ์ด๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค. TDD์ ์์น์ TypeScript ๋ฐ Jest์ ๊ธฐ๋ฅ๊ณผ ๊ฒฐํฉ๋์ด ๋ณด๋ค ํจ๊ณผ์ ์ด๊ณ ํจ์จ์ ์ธ ์ํํธ์จ์ด ๊ฐ๋ฐ ๋ผ์ดํ์ฌ์ดํด์ ์ํ ๊ฐ๋ ฅํ ๊ธฐ๋ฐ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ ์ธ๊ณ ๋ชจ๋ ์ง์ญ์์ ์ ํ์ ์ถ์ ์๊ฐ์ ๋จ์ถํ๊ณ ์ํํธ์จ์ด๋ฅผ ์๋ช ๊ธฐ๊ฐ ๋์ ๋ ์ฝ๊ฒ ์ ์ง ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
ํ์ ์์ ํ ์คํธ๋ ๋ชจ๋ ๊ตญ์ ํ์ ์ต์ ์ํํธ์จ์ด ๊ฐ๋ฐ ์ฌ๋ก์ ํ์์ ์ธ ๋ถ๋ถ์ผ๋ก ๊ฐ์ฃผ๋์ด์ผ ํฉ๋๋ค. ํ ์คํธ์ ๋ํ ํฌ์๋ ์ ํ์ ํ์ง๊ณผ ์๋ช ์ ๋ํ ํฌ์์ ๋๋ค.